package de.rj.urwalking.loader;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;

import org.poly2tri.Poly2Tri;
import org.poly2tri.geometry.polygon.Polygon;
import org.poly2tri.geometry.polygon.PolygonPoint;
import org.poly2tri.triangulation.delaunay.DelaunayTriangle;

import android.content.Context;
import android.content.SharedPreferences;
import android.location.Location;
import android.preference.PreferenceManager;

import com.threed.jpct.SimpleVector;

import de.ralf.urwalking.soplugin.EPSGPoint;
import de.rj.urwalking.R;
import de.rj.urwalking.config.Constants;
import de.rj.urwalking.data.Building;
import de.rj.urwalking.data.DoubleVector;
import de.rj.urwalking.data.Point;
import de.rj.urwalking.db.ReadWritePostgreSQL;
import de.rj.urwalking.util.GeoUtil;
import de.rj.urwalking.util.PrefUtil;

/**
 * Used to load and build the 3D objects in a separate thread.
 * 
 * @author rjj
 * 
 */

public class GeometryLoader extends AsyncLoader<ArrayList<Building>> {

	private SimpleVector offset;
	private Context context;

	/** EPSG31468-Hochwert */
	private double northing;
	/** EPSG31468-Rechtswert */
	private double easting;

	/**
	 * This constructor is used to load around an actual {@link Location}.
	 * 
	 * @param context
	 *            the activity this loader belongs to
	 * @param offset
	 * @param location
	 *            the gps location
	 */

	public GeometryLoader(Context context, SimpleVector offset, Location location) {
		super(context);
		this.context = context;
		this.offset = offset;

		// convert GPS coordinates to EPSG 31468, which is used in the database
		EPSGPoint epsgPoint = GeoUtil.convertLatLonToEPSG31468(location.getLongitude(), location.getLatitude());
		northing = epsgPoint.getY();
		easting = epsgPoint.getX();

	}

	/**
	 * This constructor is used to load around the camera position, i.e. when the user forces it.
	 * 
	 * @param context
	 *            the activity this loader belongs to
	 * @param offset
	 * @param cameraPosition
	 *            the camera position
	 */

	public GeometryLoader(Context context, SimpleVector offset, SimpleVector cameraPosition) {
		super(context);
		this.context = context;
		this.offset = offset;

		northing = offset.y + cameraPosition.y;
		easting = offset.x + cameraPosition.x;

	}

	@Override
	public ArrayList<Building> loadInBackground() {

		// connect with db
		ReadWritePostgreSQL dbWrapper = new ReadWritePostgreSQL();

		double radius = PrefUtil.readRadius(context);

		// get points for outlines of buildings
		ArrayList<Point> points = dbWrapper.getPointsWithinRadius(easting, northing, radius);

		dbWrapper.closeConnection();

		ArrayList<Building> objects = createObjects(points);

		return objects;
	}

	/**
	 * 
	 * Creates {@link Building} objects.
	 * 
	 * @param points
	 *            all points loaded from the database
	 * @return An ArrayList of Building objects
	 */

	private ArrayList<Building> createObjects(ArrayList<Point> points) {

		// do some grouping on the input data
		HashMap<Integer, ArrayList<Point>> pointsPerBuilding = groupPointsByGebid(points);

		ArrayList<Building> buildings = new ArrayList<Building>();

		for (ArrayList<Point> pointsPerObject : pointsPerBuilding.values()) {

			// sort the Points by idxP to make sure we get a nice polygon and not some mess
			Collections.sort(pointsPerObject);
			Collections.reverse(pointsPerObject);

			// using this point to get the attributes that are the same for all
			// points in this ArrayList
			Point firstPoint = pointsPerObject.get(0);

			int gebid = firstPoint.getGebid();
			double hoehegeb = firstPoint.getHoehegeb();
			String lagehnr = firstPoint.getLagehnr();
			DoubleVector centroid = new DoubleVector(firstPoint.getCentroidX(), firstPoint.getCentroidY(), hoehegeb);

			// apply world offset to centroid
			centroid.subtract(offset);

			ArrayList<SimpleVector> polygon = makePoly(pointsPerObject, hoehegeb);

			List<DelaunayTriangle> roofTriangles = null;

			if (PrefUtil.readBoolean(R.string.key_pref_roofs, context, true)){
				roofTriangles = triangulatePolygon(polygon);
			}
			

			Building building = new Building(gebid, hoehegeb, lagehnr, polygon, centroid, roofTriangles);
			buildings.add(building);
		}
		return buildings;
	}

	/**
	 * Populate a HashMap that groups objects by building id.
	 * 
	 * @param objects
	 * @return A HashMap whose keys are the building ids (gebid) and whose values are ArrayLists of Points or Triangles
	 */

	private HashMap<Integer, ArrayList<Point>> groupPointsByGebid(ArrayList<Point> objects) {
		HashMap<Integer, ArrayList<Point>> objectsPerBuilding = new HashMap<Integer, ArrayList<Point>>();

		for (int i = 0; i < objects.size(); i++) {

			Point object = objects.get(i);

			// check for first occurrence of this building id
			if (!objectsPerBuilding.containsKey(Integer.valueOf(object.getGebid()))) {
				objectsPerBuilding.put(Integer.valueOf(object.getGebid()), new ArrayList<Point>());
			}

			objectsPerBuilding.get(Integer.valueOf(object.getGebid())).add(object);
		}
		return objectsPerBuilding;
	}

	/**
	 * 
	 * @param pointsPerObject
	 * @param height
	 * @return
	 */

	private ArrayList<SimpleVector> makePoly(ArrayList<Point> pointsPerObject, double height) {

		ArrayList<SimpleVector> polygon = new ArrayList<SimpleVector>();

		for (Point point : pointsPerObject) {

			double x = point.getX() - offset.x;
			double y = point.getY() - offset.y;

			polygon.add(new SimpleVector(x, y, height));
		}
		return polygon;
	}

	private static List<DelaunayTriangle> triangulatePolygon(ArrayList<SimpleVector> polygonVectors) {

		List<PolygonPoint> polygonPoints = new ArrayList<PolygonPoint>();

		for (SimpleVector vector : polygonVectors) {
			polygonPoints.add(new PolygonPoint(vector.x, vector.y));
		}

		Polygon polygon = new Polygon(polygonPoints);
		Poly2Tri.triangulate(polygon);
		List<DelaunayTriangle> triangles = polygon.getTriangles();

		return triangles;

	}

}
